import ast
import importlib
import inspect
import sys

from .common_func.print_logger import PrintLogger as Logger
from .u_path import UPath


class UModule:
    @classmethod
    def get_method_object(cls, method_name, module_object):
        return module_object.__getattribute__(method_name)

    @staticmethod
    def import_file_according_path(py_file: str, raise_err: bool = False):
        """
        根据模块文件的路径将文件导入为模块
        使用示例：
        _module = UModule.load_file_according_module_path("/root/common/file_op.py")
        _module.func_name(xxx)

        :param py_file:
        :param raise_err: 模块不存在时是否抛出异常
        :return:
        """
        if not py_file.endswith(".py") or not UPath.path_exists(py_file):
            Logger.error(f"[UModule.import_file_according_path]::文件:{py_file} 不存在或者不是python文件")
            return None
        load_path = UPath.dir_name(py_file)
        module_name = UPath.base_name(py_file)[:-3]
        sys.path.append(load_path)

        try:
            return importlib.import_module(module_name)
        except ModuleNotFoundError as e:
            Logger.error(f"[UModule.import_file_according_path]::加载模块：{module_name}出现错误，错误信息：{e}")
            if raise_err:
                raise e
            else:
                return None

    @staticmethod
    def import_method_from_file(method_name: str, py_file: str, raise_err: bool = False):
        """
        根据模块文件的路径导入文件中的某个方法
        使用示例：
        _module = UModule.load_file_according_module_path("func", "/root/common/file_op.py")
        _module.func_name(xxx)

        :param py_file:
        :param method_name
        :param raise_err: 模块不存在时是否抛出异常
        :return:
        """
        if not py_file.endswith(".py") or not UPath.path_exists(py_file):
            Logger.error(f"[UModule.import_method_from_file]::文件:{py_file} 不存在或者不是python文件")
            return None
        load_path = UPath.dir_name(py_file)
        module_name = UPath.base_name(py_file)[:-3]
        sys.path.append(load_path)

        try:
            params = importlib.import_module(module_name, package=method_name)
            importlib.reload(params)
            target_class = params.__getattribute__(method_name)
            return target_class
        except ModuleNotFoundError as e:
            Logger.error(f"[UModule.import_method_from_file]::从文件：{py_file}中导入插件类：{method_name}，错误信息：{e}")
            if raise_err:
                raise e
            else:
                return None

    @classmethod
    def get_class_map(cls, dir_path: str, file_name_suffix: str="") -> dict:
        """
        获取指定目录下的所有python文件中的类，忽略_开头的文件内部类, 重名的类只识别一个
        返回类字典
        :param dir_path:
        :param file_name_suffix 文件名匹配后缀，
        :return: 类字典， 格式{类名: 文件绝对路径}
        """
        if not UPath.is_dir(dir_path):
            Logger.debug(f"[UModule.get_class_map]::{dir_path}不是文件夹，返回空字典")
            return dict()

        _res = dict()
        file_pattern = file_name_suffix + ".py"
        for file in UPath.file_traverse(dir_path, pattern=file_pattern):
            with open(file, "r", encoding="utf-8") as source:
                tree = ast.parse(source.read())
                for node in ast.walk(tree):
                    if not isinstance(node, ast.ClassDef) or node.name.startswith("_"):
                        continue
                    _res[node.name] = file
        Logger.debug(f"[UModule.get_class_map]::有效类映射表为：{_res}")
        return _res

    @staticmethod
    def call_class_method_by_name(cls_obj, method_name: str, **kwargs):
        """
        通过插件类名和方法名获取方法对象，用于调用该方法
        使用方法：
            kw = {"arg_name": arg_value, ...}
            _method = call_method_by_name(DemoPlugin, "demo_func", **kw)
        :param method_name: 方法名
        :param cls_obj: 类对象
        :return:
        """
        for func_name, func in inspect.getmembers(cls_obj, inspect.isfunction):
            if func_name == method_name:
                return func(**kwargs)

        for func_name, func in inspect.getmembers(cls_obj, inspect.ismethod):
            if func_name == method_name:
                return func(**kwargs)

        return None

    @staticmethod
    def reload_module(method_obj):
        """
        reload 方法或类的模块
        :param method_obj:
        :return:
        """
        module_name = sys.modules[method_obj.__module__]
        importlib.reload(module_name)
